/*
 * Copyright (c) 2020 - present, Inspur Genersoft Co., Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.iec.edp.caf.lock.service.redis;


import io.iec.edp.caf.commons.exception.ExceptionLevel;
import io.iec.edp.caf.lock.service.api.api.DistributedLock;
import io.iec.edp.caf.lock.service.api.api.DistributedLockFactory;
import io.iec.edp.caf.lock.service.api.exception.DLErrorDefinition;
import io.iec.edp.caf.lock.service.api.exception.DistributedLockException;
import lombok.extern.slf4j.Slf4j;
import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;

import java.text.SimpleDateFormat;
import java.time.Duration;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * 分布式锁创建服务实现类
 * @author wangyandong
 */
public class DistributedLockFactoryImpl implements DistributedLockFactory,AutoCloseable {

//    @Autowired串
    private static RedissonClient redisson;
    private static Object lockObj=new Object();
    private Config redissonConfig;
    private static RedissonClient getClient(Config redissonConfig)
    {
        if(redisson!=null){
            return redisson;
        }
        synchronized (lockObj){

            if(redisson!=null){
                return redisson;
            }
            redisson=  Redisson.create(redissonConfig);
            return redisson;
    }
    }
    /**
     * 构造函数
     * @param redissonConfig
     */
    public DistributedLockFactoryImpl(Config redissonConfig){
        this.redissonConfig = redissonConfig;
    }

    /**
     * Gets a RedLock using the factory's set of redis endpoints. You should check the IsAcquired property before performing actions.
     * Blocks and retries up to the specified time limits.
     * @param resource The resource string to lock on. Only one RedLock should be acquired for any given resource at once.
     * @param expiryTime How long the lock should be held for.
     * RedLocks will automatically extend if the process that created the RedLock is still alive and the RedLock hasn't been disposed.
     * @return A RedLock object.
     */

    @Override
    public DistributedLock createLock(String resource, Duration expiryTime) {

        RLock redLock = getClient(this.redissonConfig).getLock(resource);
        redLock.lock(expiryTime.getSeconds(), TimeUnit.SECONDS);
        return new DistributedLockImpl(resource, redLock);
    }

    @Override
    public DistributedLock createMultiLock(String resoucePrefix, List<String> dataIds, Duration expiryTime){
        DistributedLock distributedLock=null;
        if(dataIds!=null&&dataIds.size()>0){
            RLock[] rLocks=new RLock[dataIds.size()];
            RedissonClient redissonClient= getClient(this.redissonConfig);
            for(int i=0;i<rLocks.length;i++){
                String resource=String.format("%s-%s", resoucePrefix, dataIds.get(i));
                RLock rLock = redissonClient.getLock(resource);
                rLocks[i]=rLock;
            }
            RLock multiLock = redissonClient.getMultiLock(rLocks);
            multiLock.lock(expiryTime.getSeconds(),TimeUnit.SECONDS);
            distributedLock=new DistributedLockImpl("", multiLock);
            return distributedLock;
        }else {
            return distributedLock;
        }
    }

    @Override
    public DistributedLock tryCreateLock(Duration waitTime,String resource, Duration expiryTime) throws InterruptedException{
        RLock redLock = getClient(this.redissonConfig).getLock(resource);
        try{
            boolean  isLocked= redLock.tryLock(waitTime.getSeconds(),expiryTime.getSeconds(),TimeUnit.SECONDS);
            return new DistributedLockImpl(resource,redLock,isLocked);
        }catch (InterruptedException e){
            throw e;
        }
    }

    @Override
    public DistributedLock tryCreateLock(String resource){
        RLock redLock = getClient(this.redissonConfig).getLock(resource);
        boolean isLocked=redLock.tryLock();
        return new DistributedLockImpl(resource,redLock,isLocked);
    }

    @Override
    public DistributedLock CreateLock(String resource, Duration expiryTime) {
        return createLock(resource, expiryTime);
    }

    /**
     * Gets a RedLock using the factory's set of redis endpoints. You should check the IsAcquired property before performing actions.
     * Blocks and retries up to the specified time limits.
     * @param resource The resource string to lock on. Only one RedLock should be acquired for any given resource at once.
     * @param expiryTime How long the lock should be held for.
     * RedLocks will automatically extend if the process that created the RedLock is still alive and the RedLock hasn't been disposed.
     * @param waitTime How long to block for until a lock can be acquired.
     * @param retryTime How long to wait between retries when trying to acquire a lock.
     * @return A RedLock object.
     */
    @Deprecated
    @Override
    public DistributedLock createLock(String resource, Duration expiryTime, Duration waitTime, Duration retryTime) {

        RLock redLock = getClient(this.redissonConfig).getLock(resource);
        try {
            redLock.lock(expiryTime.getSeconds(), TimeUnit.SECONDS);
            return new DistributedLockImpl(resource, redLock);
        }catch(Exception e){
            throw new DistributedLockException("pfcomm", DLErrorDefinition.AddLock_IsAcquired_Error, null, e, ExceptionLevel.Error, false);
        }
    }

    /**
     * 关闭相关资源
     * @throws Exception
     */
    @Override
    public void close() throws Exception {
        if(this.redisson!=null)
            this.redisson.shutdown();
    }

}
